<?php

namespace App\Services;

use App\Models\Task;
use App\Repositories\ProjectRepository;
use App\Models\Project;

class Folder extends Command
{
    /**
     * 初始化宿主机部署工作空间
     *
     * @param Task $task
     * @return bool|int
     */
    public function initLocalWorkspace($task)
    {
        $version = $task["link_id"];
        // git cp 仓库, 然后 checkout 切换分支, up 到指定版本
        $cmd[] = sprintf('cp -rf %s %s ', ProjectRepository::getDeployFromDir(), ProjectRepository::getDeployWorkspace($version));
        $command = join(' && ', $cmd);
        return $this->runLocalCommand($command);
    }

    /**
     * 目标机器的版本库初始化
     * @param $version
     * @return bool
     */
    public function initRemoteVersion($version)
    {
        $command = sprintf('mkdir -p %s', ProjectRepository::getReleaseVersionDir($version));
        // ssh 循环执行远程命令
        return $this->runRemoteCommand($command);
    }

    /**
     * 将多个文件/目录通过tar + scp传输到指定的多个目标机
     *
     * @param array $project
     * @param array $task
     * @return bool
     * @throws \Exception
     */
    public function scpCopyFiles($project, $task)
    {
        // 1. 宿主机 tar 打包
        $this->_packageFiles($project, $task);

        // 2. 传输 tar.gz 文件
        foreach (ProjectRepository::getHosts() as $remoteHost) {
            // 循环 scp 传输
            $this->_copyPackageToServer($remoteHost, $project, $task);
        }

        // 3. 目标机 tar 解压
        $this->_unpackageFiles($project, $task);

        return true;
    }

    /**
     * @param array $project
     * @param array $task
     * @return bool
     * @throws \Exception
     */
    protected function _packageFiles($project, $task)
    {

        $version = $task["link_id"];
        //获取要发布的文件列表
        $files = ".";
        $excludes = str2arr($project["excludes"]);
        $packagePath = ProjectRepository::getDeployPackagePath($version);
        $packageCommand = sprintf('cd %s && tar -p %s -cz -f %s %s',
            escapeshellarg(rtrim(ProjectRepository::getDeployWorkspace($version), '/') . '/'),
            $this->excludes($excludes),
            escapeshellarg($packagePath),
            $files
        );
        $ret = $this->runLocalCommand($packageCommand);
        if (!$ret) {
            throw new \Exception(trans("alerts.deploy.package_error"));
        }

        return true;
    }

    /**
     * @param $remoteHost
     * @param array $project
     * @param array $task
     * @return bool
     * @throws \Exception
     */
    protected function _copyPackageToServer($remoteHost, $project, $task)
    {

        $version = $task["link_id"];
        $packagePath = ProjectRepository::getDeployPackagePath($version);

        $releasePackage = ProjectRepository::getReleaseVersionPackage($version);

        $scpCommand = sprintf('scp -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o CheckHostIP=false -P %d %s %s@%s:%s',
            $this->getHostPort($remoteHost),
            $packagePath,
            escapeshellarg($this->getConfig()["release_user"]),
            escapeshellarg($this->getHostName($remoteHost)),
            $releasePackage);

        $ret = $this->runLocalCommand($scpCommand);

        if (!$ret) {
            throw new \Exception(trans("alerts.deploy.rsync_error"));
        }

        return true;
    }

    /**
     * @param array $project
     * @param array $task
     * @return bool
     * @throws \Exception
     */
    protected function _unpackageFiles($project, $task)
    {

        $version = $task["link_id"];
        $releasePackage = ProjectRepository::getReleaseVersionPackage($version);

        $releasePath = ProjectRepository::getReleaseVersionDir($version);

        $cmd[] = sprintf('cd %1$s && tar --no-same-owner -pm -C %1$s -xz -f %2$s',
            $releasePath,
            $releasePackage
        );

        $command = join(' && ', $cmd);

        $ret = $this->runRemoteCommand($command);

        if (!$ret) {
            throw new \Exception(trans("alerts.deploy.unpackage_error"));
        }

        return true;
    }

    /**
     * 打软链
     *
     * @param null $version
     * @return bool
     */
    public function getLinkCommand($version)
    {
        $user = $this->getConfig()["release_user"];
        $project = ProjectRepository::getGitProjectName($this->getConfig()["repo_url"]);
        $currentTmp = sprintf('%s/%s/current-%s.tmp', rtrim($this->getConfig()["release_library"], '/'), $project, $project);
        // 遇到回滚，则使用回滚的版本version
        $linkFrom = ProjectRepository::getReleaseVersionDir($version);
        $cmd[] = sprintf('ln -sfn %s %s', $linkFrom, $currentTmp);
        $cmd[] = sprintf('chown -h %s %s', $user, $currentTmp);
        $cmd[] = sprintf('mv -fT %s %s', $currentTmp, $this->getConfig()["release_to"]);

        return join(' && ', $cmd);
    }

    /**
     * 获取文件的MD5
     *
     * @param $file
     * @return bool
     */
    public function getFileMd5($file)
    {
        $cmd[] = "test -f /usr/bin/md5sum && md5sum {$file}";
        $command = join(' && ', $cmd);
        return $this->runRemoteCommand($command);
    }

    /**
     * rsync时，要排除的文件
     *
     * @param array $excludes
     * @return string
     */
    protected function excludes($excludes)
    {

        $excludesCmd = '';

        // 无论是否填写排除.git和.svn, 这两个目录都不会发布
        array_push($excludes, '.git');
        array_push($excludes, '.svn');

        $excludes = array_unique($excludes);
        foreach ($excludes as $exclude) {
            $excludesCmd .= sprintf("--exclude=%s ", escapeshellarg(trim($exclude)));
        }

        return trim($excludesCmd);
    }

    /**
     * 收尾做处理工作，如清理本地的部署空间
     *
     * @param $version
     * @return bool|int
     */
    public function cleanUpLocal($version)
    {
        $cmd[] = 'rm -rf ' . ProjectRepository::getDeployWorkspace($version);
        $cmd[] = sprintf('rm -f %s/*.tar.gz', dirname(ProjectRepository::getDeployPackagePath($version)));
        $command = join(' && ', $cmd);
        return $this->runLocalCommand($command);
    }

    /**
     * 删除本地项目空间
     *
     * @param $projectDir
     * @return bool|int
     */
    public function removeLocalProjectWorkspace($projectDir)
    {
        $cmd[] = "rm -rf " . $projectDir;
        $command = join(' && ', $cmd);
        return $this->runLocalCommand($command);
    }
}

